Pythonã§ã®äžŠè¡ããã°ã©ãã³ã°ã®åãè§£ãæŸã¡ãŸãããã髿§èœã§ã¹ã±ãŒã©ãã«ãªã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããããã«ãAsyncioã¿ã¹ã¯ã®äœæã管çããã£ã³ã»ã«æ¹æ³ãåŠã³ãŸãã
Mastering Python Asyncio: A Deep Dive into Task Creation and Management
çŸä»£ã®ãœãããŠã§ã¢éçºã®äžçã§ã¯ãããã©ãŒãã³ã¹ãæãéèŠã§ããã¢ããªã±ãŒã·ã§ã³ã¯ãäœåãã®åæãããã¯ãŒã¯æ¥ç¶ãããŒã¿ããŒã¹ã¯ãšãªãAPIåŒã³åºããé£ãªãåŠçããå¿çæ§ãé«ãããšãæ±ããããŸããI/OããŠã³ãã®æäœâããã°ã©ã ããããã¯ãŒã¯ããã£ã¹ã¯ãªã©ã®å€éšãªãœãŒã¹ãåŸ
ã€æéãã»ãšãã©ã®å Žåâã§ã¯ãåŸæ¥ã®åæã³ãŒããé倧ãªããã«ããã¯ã«ãªãå¯èœæ§ããããŸããããã§éåæããã°ã©ãã³ã°ãåšåãçºæ®ããPythonã®asyncio
ã©ã€ãã©ãªããã®åãè§£æŸããéµãšãªããŸãã
asyncio
ã®äžŠè¡åŠçã¢ãã«ã®äžæ žã«ã¯ãã·ã³ãã«ã§ãããªãã匷åãªæŠå¿µããããŸããããã¯ã¿ã¹ã¯ã§ããã³ã«ãŒãã³ã¯äœããã¹ãããå®çŸ©ããŸãããã¿ã¹ã¯ã¯å®éã«ç©äºãå®è¡ãããã®ã§ãããããã¯äžŠè¡å®è¡ã®åºæ¬çãªåäœã§ãããPythonããã°ã©ã ãè€æ°ã®æäœãåæã«åŠçã§ããããã«ããã¹ã«ãŒããããšå¿çæ§ãåçã«åäžãããŸãã
ãã®å
æ¬çãªã¬ã€ãã§ã¯ãasyncio.Task
ãæ·±ãæãäžããŠãããŸããäœæã®åºæ¬ããé«åºŠãªç®¡çãã¿ãŒã³ããã£ã³ã»ã«ããã¹ããã©ã¯ãã£ã¹ãŸã§ããããããã®ãæ¢æ±ããŸããé«ãã©ãã£ãã¯ã®WebãµãŒãã¹ãããŒã¿ã¹ã¯ã¬ã€ãã³ã°ããŒã«ããŸãã¯ãªã¢ã«ã¿ã€ã ã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããå Žåã§ããã¿ã¹ã¯ãç¿åŸããããšã¯ãçŸä»£ã®Pythonéçºè
ã«ãšã£ãŠäžå¯æ¬ ãªã¹ãã«ã§ãã
What is a Coroutine? A Quick Refresher
èµ°ãåã«ãæ©ããªããã°ãªããŸããããããŠãasyncio
ã®äžçã§ã¯ãæ©ãããšã¯ã³ã«ãŒãã³ãçè§£ããããšã§ããã³ã«ãŒãã³ã¯ãasync def
ã§å®çŸ©ãããç¹æ®ãªã¿ã€ãã®é¢æ°ã§ãã
éåžžã®Python颿°ãåŒã³åºããšãæåããæåŸãŸã§å®è¡ãããŸãããã ããã³ã«ãŒãã³é¢æ°ãåŒã³åºããšãããã«å®è¡ãããŸããã代ããã«ãã³ã«ãŒãã³ãªããžã§ã¯ããè¿ããŸãããã®ãªããžã§ã¯ãã¯ãå®è¡ãããäœæ¥ã®èšèšå³ã§ãããããèªäœã¯äžæŽ»æ§ã§ããããã¯ãéå§ãäžæãããã³åéã§ããäžæåæ¢ãããèšç®ã§ãã
import asyncio
async def say_hello(name: str):
print(f"Preparing to greet {name}...")
await asyncio.sleep(1) # Simulate a non-blocking I/O operation
print(f"Hello, {name}!")
# Calling the function doesn't run it, it creates a coroutine object
coro = say_hello("World")
print(f"Created a coroutine object: {coro}")
# To actually run it, you need to use an entry point like asyncio.run()
# asyncio.run(coro)
éæ³ã®ããŒã¯ãŒãã¯await
ã§ããããã¯ã€ãã³ãã«ãŒãã«ãããã®æäœã«ã¯æéããããå¯èœæ§ããããããããã§äžæåæ¢ããŠãä»ã®äœæ¥ã«åãæãã£ãŠãã ããããã®æäœãå®äºãããèµ·ãããŠãã ããããšäŒããŸãããã®äžæåæ¢ããŠã³ã³ããã¹ããåãæ¿ããæ©èœãã䞊è¡åŠçãå¯èœã«ããŸãã
The Heart of Concurrency: Understanding asyncio.Task
ã§ã¯ãã³ã«ãŒãã³ã¯èšèšå³ã§ãããããã³ïŒã€ãã³ãã«ãŒãïŒã«æçãå§ããããã«æç€ºããã«ã¯ã©ãããã°ããã§ããããïŒããã§asyncio.Task
ãç»å ŽããŸãã
asyncio.Task
ã¯ãã³ã«ãŒãã³ãã©ããããasyncioã€ãã³ãã«ãŒãã§ã®å®è¡ãã¹ã±ãžã¥ãŒã«ãããªããžã§ã¯ãã§ããæ¬¡ã®ããã«èããŠãã ããã
- Coroutine (
async def
): æçã®è©³çްãªã¬ã·ãã - Event Loop: ãã¹ãŠã®æçãè¡ãããäžå€®ã®ãããã³ã
await my_coro()
: ãããã³ã«ç«ã£ãŠãã¬ã·ããæ®µéçã«èªåã§å®è¡ããŸããæçãå®äºãããŸã§ãä»ã®ããšã¯äœãã§ããŸãããããã¯é次å®è¡ã§ããasyncio.create_task(my_coro())
: ã¬ã·ãããããã³ã®ã·ã§ãïŒã¿ã¹ã¯ïŒã«æž¡ãããããã«åãæãã£ãŠãã ããããšèšããŸããã·ã§ãã¯ããã«éå§ããããªãã¯ä»ã®ã¬ã·ããé ããªã©ãèªç±ã«ä»ã®ããšãè¡ãããšãã§ããŸããããã¯äžŠè¡å®è¡ã§ãã
éèŠãªéãã¯ãasyncio.create_task()
ãã³ã«ãŒãã³ããããã¯ã°ã©ãŠã³ããã§å®è¡ããããã«ã¹ã±ãžã¥ãŒã«ããããã«ã³ãŒãã«å¶åŸ¡ãè¿ãããšã§ããTask
ãªããžã§ã¯ããè¿ãããŸããããã¯ããã®é²è¡äžã®æäœãžã®ãã³ãã«ãšããŠæ©èœããŸãããã®ãã³ãã«ã䜿çšããŠãã¹ããŒã¿ã¹ã確èªãããããã£ã³ã»ã«ããããåŸã§çµæãåŸ
ã€ããšãã§ããŸãã
Creating Your First Tasks: The `asyncio.create_task()` Function
ã¿ã¹ã¯ãäœæããäž»ãªæ¹æ³ã¯ãasyncio.create_task()
颿°ã䜿çšããããšã§ããããã¯ãåŒæ°ãšããŠã³ã«ãŒãã³ãªããžã§ã¯ããåããå®è¡ã®ããã«ã¹ã±ãžã¥ãŒã«ããŸãã
The Basic Syntax
äœ¿ãæ¹ã¯ç°¡åã§ãïŒ
import asyncio
async def my_background_work():
print("Starting background work...")
await asyncio.sleep(2)
print("Background work finished.")
return "Success"
async def main():
print("Main function started.")
# Schedule my_background_work to run concurrently
task = asyncio.create_task(my_background_work())
# While the task runs, we can do other things
print("Task created. Main function continues to run.")
await asyncio.sleep(1)
print("Main function did some other work.")
# Now, wait for the task to complete and get its result
result = await task
print(f"Task completed with result: {result}")
asyncio.run(main())
åºåãèŠããšã`main`颿°ãã¿ã¹ã¯ãäœæããçŽåŸã«å®è¡ãç¶ç¶ããŠããããšãããããŸãããããã¯ããŸãããæåŸã«æç€ºçã«`await task`ãããšãã«ã®ã¿äžæåæ¢ããŸãã
A Practical Example: Concurrent Web Requests
äžè¬çãªã·ããªãªã§ããè€æ°ã®URLããããŒã¿ããã§ããããããšã§ãã¿ã¹ã¯ã®çã®åã確èªããŸããããããã«ã¯ã`pip install aiohttp`ã§ã€ã³ã¹ããŒã«ã§ããäžè¬çãª`aiohttp`ã©ã€ãã©ãªã䜿çšããŸãã
ãŸããã·ãŒã±ã³ã·ã£ã«ïŒé ãïŒæ¹æ³ãèŠãŠã¿ãŸãããïŒ
import asyncio
import aiohttp
import time
async def fetch_status(session, url):
async with session.get(url) as response:
return response.status
async def main_sequential():
urls = [
"https://www.python.org",
"https://www.google.com",
"https://www.github.com",
"https://www.microsoft.com"
]
start_time = time.time()
async with aiohttp.ClientSession() as session:
for url in urls:
status = await fetch_status(session, url)
print(f"Status for {url}: {status}")
end_time = time.time()
print(f"Sequential execution took {end_time - start_time:.2f} seconds")
# To run this, you would use: asyncio.run(main_sequential())
åãªã¯ãšã¹ãã«çŽ0.5ç§ãããå Žåãå`await`ã¯ãã®åäžã®ãªã¯ãšã¹ããå®äºãããŸã§ã«ãŒãããããã¯ãããããåèšæéã¯çŽ2ç§ã«ãªããŸãã
次ã«ãã¿ã¹ã¯ã䜿çšããŠäžŠè¡åŠçã®åãè§£ãæŸã¡ãŸãããïŒ
import asyncio
import aiohttp
import time
# fetch_status coroutine remains the same
async def fetch_status(session, url):
async with session.get(url) as response:
return response.status
async def main_concurrent():
urls = [
"https://www.python.org",
"https://www.google.com",
"https://www.github.com",
"https://www.microsoft.com"
]
start_time = time.time()
async with aiohttp.ClientSession() as session:
# Create a list of tasks, but don't await them yet
tasks = [asyncio.create_task(fetch_status(session, url)) for url in urls]
# Now, wait for all tasks to complete
statuses = await asyncio.gather(*tasks)
for url, status in zip(urls, statuses):
print(f"Status for {url}: {status}")
end_time = time.time()
print(f"Concurrent execution took {end_time - start_time:.2f} seconds")
asyncio.run(main_concurrent())
䞊è¡ããŒãžã§ã³ãå®è¡ãããšãåçãªéãã衚瀺ãããŸããåèšæéã¯ããã¹ãŠãåèšããæéã§ã¯ãªããæé·ã®åäžãªã¯ãšã¹ãã®æéãšã»ãŒåãã«ãªããŸããããã¯ãæåã®`fetch_status`ã³ã«ãŒãã³ã`await session.get(url)`ã«éãããšããã«ãã€ãã³ãã«ãŒãããããäžæåæ¢ããããã«æ¬¡ã®ã³ã«ãŒãã³ãéå§ããããã§ãããã¹ãŠã®ãããã¯ãŒã¯ãªã¯ãšã¹ãã¯ãäºå®äžåæã«è¡ãããŸãã
Managing a Group of Tasks: Essential Patterns
åã ã®ã¿ã¹ã¯ãäœæããã®ã¯çŽ æŽãããããšã§ãããå®éã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãå€ãã®å Žåãã¿ã¹ã¯ã®ã°ã«ãŒãå šäœãèµ·åã管çãããã³åæããå¿ èŠããããŸãã`asyncio`ã¯ãããã®ããã®ããã€ãã®åŒ·åãªããŒã«ãæäŸããŸãã
The Modern Approach (Python 3.11+): `asyncio.TaskGroup`
Python 3.11ã§å°å ¥ããã`TaskGroup`ã¯ãé¢é£ããã¿ã¹ã¯ã®ã°ã«ãŒãã管çããããã®æ°ãããæšå¥šãããããããŠæãå®å šãªæ¹æ³ã§ããããã¯ãæ§é åããã䞊è¡åŠçãšããŠç¥ãããŠãããã®ãæäŸããŸãã
`TaskGroup`ã®äž»ãªæ©èœïŒ
- ä¿èšŒãããã¯ãªãŒã³ã¢ããïŒ
async with
ãããã¯ã¯ããã®äžã«äœæããããã¹ãŠã®ã¿ã¹ã¯ãå®äºãããŸã§çµäºããŸããã - å ç¢ãªãšã©ãŒåŠçïŒã°ã«ãŒãå ã®ããããã®ã¿ã¹ã¯ãäŸå€ãçºçãããå Žåãã°ã«ãŒãå ã®ä»ã®ãã¹ãŠã®ã¿ã¹ã¯ã¯èªåçã«ãã£ã³ã»ã«ãããäŸå€ïŒãŸãã¯`ExceptionGroup`ïŒã¯`async with`ãããã¯ã®çµäºæã«å床çºçããŸããããã«ãããå€ç«ããã¿ã¹ã¯ã鲿¢ãããäºæž¬å¯èœãªç¶æ ãä¿èšŒãããŸãã
äœ¿çšæ¹æ³ã¯æ¬¡ã®ãšããã§ãïŒ
import asyncio
async def worker(delay):
print(f"Worker starting, will sleep for {delay}s")
await asyncio.sleep(delay)
# This worker will fail
if delay == 2:
raise ValueError("Something went wrong in worker 2")
print(f"Worker with delay {delay} finished")
return f"Result from {delay}s"
async def main():
print("Starting main with TaskGroup...")
try:
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(worker(1))
task2 = tg.create_task(worker(2)) # This one will fail
task3 = tg.create_task(worker(3))
print("Tasks created in the group.")
# This part of the code will NOT be reached if an exception occurs
# The results would be accessed via task1.result(), etc.
print("All tasks completed successfully.")
except* ValueError as eg: # Note the `except*` for ExceptionGroup
print(f"Caught an exception group with {len(eg.exceptions)} exceptions.")
for exc in eg.exceptions:
print(f" - {exc}")
print("Main function finished.")
asyncio.run(main())
ãããå®è¡ãããšã`worker(2)`ããšã©ãŒãçºçãããããšãããããŸãã`TaskGroup`ã¯ããããã£ããããå®è¡äžã®ä»ã®ã¿ã¹ã¯ïŒ`worker(3)`ãªã©ïŒããã£ã³ã»ã«ãã`ValueError`ãå«ã`ExceptionGroup`ãçºçãããŸãããã®ãã¿ãŒã³ã¯ãä¿¡é Œæ§ã®é«ãã·ã¹ãã ãæ§ç¯ããäžã§éåžžã«å ç¢ã§ãã
The Classic Workhorse: `asyncio.gather()`
`TaskGroup`ã®åã«ã`asyncio.gather()`ã¯ãè€æ°ã®awaitableãåæã«å®è¡ãããããããã¹ãŠå®äºããã®ãåŸ ã€ããã®æãäžè¬çãªæ¹æ³ã§ããã
`gather()`ã¯ãã³ã«ãŒãã³ãŸãã¯ã¿ã¹ã¯ã®ã·ãŒã±ã³ã¹ãåãåãããããããã¹ãŠå®è¡ããå ¥åãšåãé åºã§çµæã®ãªã¹ããè¿ããŸããããã¯ããããããã¹ãŠãå®è¡ããŠããã¹ãŠã®çµæãååŸããããšããäžè¬çãªã±ãŒã¹ã®ããã®é«ã¬ãã«ã§äŸ¿å©ãªé¢æ°ã§ãã
import asyncio
async def fetch_data(source, delay):
print(f"Fetching from {source}...")
await asyncio.sleep(delay)
return {"source": source, "data": f"some data from {source}"}
async def main():
# gather can take coroutines directly
results = await asyncio.gather(
fetch_data("API", 2),
fetch_data("Database", 3),
fetch_data("Cache", 1)
)
print(results)
asyncio.run(main())
`gather()`ã䜿çšãããšã©ãŒåŠçïŒããã©ã«ãã§ã¯ã`gather()`ã«æž¡ãããawaitableã®ãããããäŸå€ãçºçãããå Žåã`gather()`ã¯ãã®äŸå€ãããã«äŒæããä»ã®å®è¡äžã®ã¿ã¹ã¯ã¯ãã£ã³ã»ã«ãããŸãã`return_exceptions=True`ã䜿çšããŠããã®åäœã倿Žã§ããŸãããã®ã¢ãŒãã§ã¯ãäŸå€ãçºçããã代ããã«ã察å¿ããäœçœ®ã®çµæãªã¹ãã«é 眮ãããŸãã
# ... inside main()
results = await asyncio.gather(
fetch_data("API", 2),
asyncio.create_task(worker(1)), # This will raise a ValueError
fetch_data("Cache", 1),
return_exceptions=True
)
# results will contain a mix of successful results and exception objects
print(results)
Fine-Grained Control: `asyncio.wait()`
asyncio.wait()`ã¯ãã¿ã¹ã¯ã®ã°ã«ãŒãããã詳现ã«å¶åŸ¡ã§ããäžäœã¬ãã«ã®é¢æ°ã§ãã`gather()`ãšã¯ç°ãªããçµæãçŽæ¥è¿ããŸããã代ããã«ã`done`ãš`pending`ã®2ã€ã®ã¿ã¹ã¯ã»ãããè¿ããŸãã
æã匷åãªæ©èœã¯ã`return_when`ãã©ã¡ãŒã¿ãŒã§ããããã¯æ¬¡ã®ããããã«ãªããŸãïŒ
asyncio.ALL_COMPLETED
(default): ãã¹ãŠã®ã¿ã¹ã¯ãå®äºãããšè¿ãããŸããasyncio.FIRST_COMPLETED
: å°ãªããšã1ã€ã®ã¿ã¹ã¯ãå®äºãããšããã«è¿ãããŸããasyncio.FIRST_EXCEPTION
: ã¿ã¹ã¯ãäŸå€ãçºçããããšè¿ãããŸããã¿ã¹ã¯ãäŸå€ãçºçãããªãå Žåã`ALL_COMPLETED`ãšåçã§ãã
ããã¯ãè€æ°ã®åé·ããŒã¿ãœãŒã¹ãã¯ãšãªããæåã«å¿çããããŒã¿ãœãŒã¹ã䜿çšãããªã©ã®ã·ããªãªã«éåžžã«åœ¹ç«ã¡ãŸãïŒ
import asyncio
async def query_source(name, delay):
await asyncio.sleep(delay)
return f"Result from {name}"
async def main():
tasks = [
asyncio.create_task(query_source("Fast Mirror", 0.5)),
asyncio.create_task(query_source("Slow Main DB", 2.0)),
asyncio.create_task(query_source("Geographic Replica", 0.8))
]
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
# Get the result from the completed task
first_result = done.pop().result()
print(f"Got first result: {first_result}")
# We now have pending tasks that are still running. It's crucial to clean them up!
print(f"Cancelling {len(pending)} pending tasks...")
for task in pending:
task.cancel()
# Await the cancelled tasks to allow them to process the cancellation
await asyncio.gather(*pending, return_exceptions=True)
print("Cleanup complete.")
asyncio.run(main())
TaskGroup vs. gather() vs. wait(): When to Use Which?
- `asyncio.TaskGroup` (Python 3.11+) ãããã©ã«ãã®éžæè¢ãšããŠäœ¿çšããŸãããã®æ§é åããã䞊è¡åŠçã¢ãã«ã¯ãåäžã®è«çæäœã«å±ããã¿ã¹ã¯ã®ã°ã«ãŒãã管çããäžã§ãããå®å šã§ãããã¯ãªãŒã³ã§ããšã©ãŒãçºçãã«ããã§ãã
- `asyncio.gather()` ã¯ãç¬ç«ããã¿ã¹ã¯ã®ã°ã«ãŒããå®è¡ãããã®çµæã®ãªã¹ããåçŽã«å¿ èŠãšããå Žåã«äœ¿çšããŸããç¹ã«Python 3.11ããåã®ããŒãžã§ã³ã§ã¯ãåçŽãªã±ãŒã¹ã§ã¯éåžžã«äŸ¿å©ã§ããããã«ç°¡æœã§ãã
- `asyncio.wait()` ã¯ãå®äºæ¡ä»¶ïŒæåã®çµæãåŸ ã€ãªã©ïŒã现ããå¶åŸ¡ããå¿ èŠããããæ®ãã®ä¿çäžã®ã¿ã¹ã¯ãæåã§ç®¡çããæºåãã§ããŠããé«åºŠãªã·ããªãªã«äœ¿çšããŸãã
Task Lifecycle and Management
ã¿ã¹ã¯ãäœæããããšã`Task`ãªããžã§ã¯ãã®ã¡ãœããã䜿çšããŠã¿ã¹ã¯ãšå¯Ÿè©±ã§ããŸãã
Checking Task Status
task.done()
: ã¿ã¹ã¯ãå®äºããå ŽåïŒæåãäŸå€ããŸãã¯ãã£ã³ã»ã«ã«ããïŒã¯`True`ãè¿ããŸããtask.cancelled()
: ã¿ã¹ã¯ããã£ã³ã»ã«ãããå Žåã¯`True`ãè¿ããŸããtask.exception()
: ã¿ã¹ã¯ãäŸå€ãçºçãããå Žåãããã¯äŸå€ãªããžã§ã¯ããè¿ããŸãããã以å€ã®å Žåã¯ã`None`ãè¿ããŸããããã¯ãã¿ã¹ã¯ã`done()`ã«ãªã£ãåŸã«ã®ã¿åŒã³åºãããšãã§ããŸãã
Retrieving Results
ã¿ã¹ã¯ã®çµæãååŸããäž»ãªæ¹æ³ã¯ãåã«`await task`ã䜿çšããããšã§ããã¿ã¹ã¯ãæ£åžžã«çµäºããå Žåãããã¯å€ãè¿ããŸããäŸå€ãçºçããå Žåã`await task`ã¯ãã®äŸå€ãå床çºçãããŸãããã£ã³ã»ã«ãããå Žåã`await task`ã¯`CancelledError`ãçºçãããŸãã
ãŸãã¯ãã¿ã¹ã¯ã`done()`ã§ããããšãç¥ã£ãŠããå Žåã¯ã`task.result()`ãåŒã³åºãããšãã§ããŸããããã¯ãå€ãè¿ããäŸå€ãçºçããããšããç¹ã§ã`await task`ãšåäžã«åäœããŸãã
The Art of Cancellation
é·æéå®è¡ãããæäœãæ£åžžã«ãã£ã³ã»ã«ã§ããããšã¯ãå ç¢ãªã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããäžã§éèŠã§ããã¿ã€ã ã¢ãŠãããŠãŒã¶ãŒãªã¯ãšã¹ãããŸãã¯ã·ã¹ãã å ã®å¥ã®å Žæã§ã®ãšã©ãŒã«ãããã¿ã¹ã¯ããã£ã³ã»ã«ããå¿ èŠãããå ŽåããããŸãã
ã¿ã¹ã¯ããã£ã³ã»ã«ããã«ã¯ãtask.cancel()
ã¡ãœãããåŒã³åºããŸãããã ããããã¯ã¿ã¹ã¯ãããã«åæ¢ããŸããã代ããã«ã次ã®await
ãã€ã³ãã§ã³ã«ãŒãã³å
ã§`CancelledError`äŸå€ãã¹ããŒãããããã«ã¹ã±ãžã¥ãŒã«ããŸããããã¯éèŠãªè©³çްã§ããããã«ãããã³ã«ãŒãã³ã¯çµäºããåã«ã¯ãªãŒã³ã¢ããããæ©äŒãåŸãããŸãã
è¡åã®è¯ãã³ã«ãŒãã³ã¯ããã®`CancelledError`ãé©åã«åŠçããå¿ èŠããããŸããéåžžã¯ã`try...finally`ãããã¯ã䜿çšããŠããã¡ã€ã«ãã³ãã«ãããŒã¿ããŒã¹æ¥ç¶ãªã©ã®ãªãœãŒã¹ãéããããŠããããšã確èªããŸãã
import asyncio
async def resource_intensive_task():
print("Acquiring resource (e.g., opening a connection)...")
try:
for i in range(10):
print(f"Working... step {i+1}")
await asyncio.sleep(1) # This is an await point where CancelledError can be injected
except asyncio.CancelledError:
print("Task was cancelled! Cleaning up...")
raise # It's good practice to re-raise CancelledError
finally:
print("Releasing resource (e.g., closing connection). This always runs.")
async def main():
task = asyncio.create_task(resource_intensive_task())
# Let it run for a bit
await asyncio.sleep(2.5)
print("Main decides to cancel the task.")
task.cancel()
try:
await task
except asyncio.CancelledError:
print("Main has confirmed the task was cancelled.")
asyncio.run(main())
`finally`ãããã¯ã¯å¿ ãå®è¡ããããããã¯ãªãŒã³ã¢ããããžãã¯ã«æé©ãªå Žæã§ãã
Adding Timeouts with `asyncio.timeout()` and `asyncio.wait_for()`
æåã§ã¹ãªãŒãããŠãã£ã³ã»ã«ããã®ã¯é¢åã§ãã`asyncio`ã¯ããã®äžè¬çãªãã¿ãŒã³ã®ããã®ãã«ããŒãæäŸããŸãã
Python 3.11+ã§ã¯ã`asyncio.timeout()`ã³ã³ããã¹ããããŒãžã£ãŒãæšå¥šãããæ¹æ³ã§ãïŒ
async def long_running_operation():
await asyncio.sleep(10)
print("Operation finished")
async def main():
try:
async with asyncio.timeout(2): # Set a 2-second timeout
await long_running_operation()
except TimeoutError:
print("The operation timed out!")
asyncio.run(main())
å€ãPythonããŒãžã§ã³ã§ã¯ã`asyncio.wait_for()`ã䜿çšã§ããŸããåäœã¯äŒŒãŠããŸãããawaitableã颿°åŒã³åºãã§ã©ããããŸãïŒ
async def main_legacy():
try:
await asyncio.wait_for(long_running_operation(), timeout=2)
except asyncio.TimeoutError:
print("The operation timed out!")
asyncio.run(main_legacy())
ã©ã¡ãã®ããŒã«ããã¿ã€ã ã¢ãŠãã«éãããšå éšã¿ã¹ã¯ããã£ã³ã»ã«ãã`TimeoutError`ïŒ`CancelledError`ã®ãµãã¯ã©ã¹ïŒãçºçãããããšã«ãã£ãŠæ©èœããŸãã
Common Pitfalls and Best Practices
ã¿ã¹ã¯ã®æäœã¯åŒ·åã§ãããé¿ããã¹ãããã€ãã®äžè¬çãªèœãšã穎ããããŸãã
- èœãšã穎ïŒããã¡ã€ã¢ã¢ã³ããã©ãŒã²ãããã®èª€ãã `create_task`ã§ã¿ã¹ã¯ãäœæãããããïŒãŸãã¯`TaskGroup`ã®ãããªãããŒãžã£ãŒãïŒæ±ºããŠåŸ æ©ããªãã®ã¯å±éºã§ãããã®ã¿ã¹ã¯ãäŸå€ãçºçãããå ŽåãäŸå€ã¯ã²ã£ãããšå€±ãããã¿ã¹ã¯ãäœæ¥ãå®äºããåã«ããã°ã©ã ãçµäºããå¯èœæ§ããããŸããåžžã«ãçµæãåŸ æ©ãã責任ãè² ããã¹ãŠã®ã¿ã¹ã¯ã«æç¢ºãªãªãŒããŒãå¿ èŠã§ãã
- èœãšã穎ïŒ`asyncio.run()`ãš`create_task()`ã®æ··åã `asyncio.run(my_coro())`ã¯ã`asyncio`ããã°ã©ã ãéå§ããããã®äž»èŠãªãšã³ããªãã€ã³ãã§ããããã¯ãæ°ããã€ãã³ãã«ãŒããäœæããæå®ãããã³ã«ãŒãã³ãå®äºãããŸã§å®è¡ããŸãã `asyncio.create_task(my_coro())`ã¯ã䞊è¡å®è¡ãã¹ã±ãžã¥ãŒã«ããããã«ããã§ã«å®è¡äžã®éåæé¢æ°å ã§äœ¿çšãããŸãã
- ãã¹ããã©ã¯ãã£ã¹ïŒææ°ã®Pythonã«ã¯`TaskGroup`ã䜿çšããŸãã ãã®èšèšã¯ãå¿ããããã¿ã¹ã¯ãåŠçãããŠããªãäŸå€ãªã©ãå€ãã®äžè¬çãªãšã©ãŒãé²ããŸãã Python 3.11以éã䜿çšããŠããå Žåã¯ããããããã©ã«ãã®éžæè¢ã«ããŠãã ããã
- ãã¹ããã©ã¯ãã£ã¹ïŒã¿ã¹ã¯ã«ååãä»ããŸãã ã¿ã¹ã¯ãäœæãããšãã¯ã`name`ãã©ã¡ãŒã¿ãŒã䜿çšããŸãïŒ`asyncio.create_task(my_coro(), name='DataProcessor-123')`ãããã¯ãããã°ã«éåžžã«åœ¹ç«ã¡ãŸããå®è¡äžã®ãã¹ãŠã®ã¿ã¹ã¯ããªã¹ããããšãã«ãæå³ã®ããååãä»ãããšãããã°ã©ã ãäœãããŠããããçè§£ããã®ã«åœ¹ç«ã¡ãŸãã
- ãã¹ããã©ã¯ãã£ã¹ïŒæ£åžžãªã·ã£ããããŠã³ãä¿èšŒããŸãã ã¢ããªã±ãŒã·ã§ã³ãã·ã£ããããŠã³ããå¿ èŠãããå Žåã¯ãå®è¡äžã®ãã¹ãŠã®ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ããã£ã³ã»ã«ããããããé©åã«ã¯ãªãŒã³ã¢ãããããã®ãåŸ ã€ã¡ã«ããºã ãããããšã確èªããŠãã ããã
Advanced Concepts: A Glimpse Beyond
ãããã°ãšã€ã³ããã¹ãã¯ã·ã§ã³ã®ããã«ã`asyncio`ã¯ããã€ãã®äŸ¿å©ãªé¢æ°ãæäŸããŸãïŒ
asyncio.current_task()
: çŸåšå®è¡ãããŠããã³ãŒãã®`Task`ãªããžã§ã¯ããè¿ããŸããasyncio.all_tasks()
: ã€ãã³ãã«ãŒãã«ãã£ãŠçŸåšç®¡çãããŠãããã¹ãŠã®`Task`ãªããžã§ã¯ãã®ã»ãããè¿ããŸããããã¯ãäœãå®è¡ãããŠãããã確èªããããã®ãããã°ã«æé©ã§ãã
`task.add_done_callback()`ã䜿çšããŠãå®äºã³ãŒã«ããã¯ãã¿ã¹ã¯ã«ã¢ã¿ããããããšãã§ããŸããããã¯åœ¹ç«ã€å ŽåããããŸãããå€ãã®å Žåãããè€éãªã³ãŒã«ããã¯ã¹ã¿ã€ã«ã®ã³ãŒãæ§é ã«ã€ãªãããŸãã `await`ã`TaskGroup`ããŸãã¯`gather`ã䜿çšããææ°ã®ã¢ãããŒãã¯ãäžè¬ã«ãèªã¿ããããšä¿å®æ§ã®ããã«æšå¥šãããŸãã
Conclusion
`asyncio.Task`ã¯ãçŸä»£ã®Pythonã«ããã䞊è¡åŠçã®ãšã³ãžã³ã§ããã¿ã¹ã¯ã®äœæã管çãããã³ã©ã€ããµã€ã¯ã«ãé©åã«åŠçããæ¹æ³ãçè§£ããããšã§ãI/OããŠã³ãã®ã¢ããªã±ãŒã·ã§ã³ããé ãã·ãŒã±ã³ã·ã£ã«ããã»ã¹ãããéåžžã«å¹ççã§ãã¹ã±ãŒã©ãã«ã§ãå¿çæ§ã®é«ãã·ã¹ãã ã«å€ããããšãã§ããŸãã
`create_task()`ã䜿çšããã³ã«ãŒãã³ã®ã¹ã±ãžã¥ãŒãªã³ã°ãšããåºæ¬çãªæŠå¿µããã`TaskGroup`ã`gather()`ãããã³`wait()`ã䜿çšããè€éãªã¯ãŒã¯ãããŒã®ãªãŒã±ã¹ãã¬ãŒã·ã§ã³ãŸã§ã説æããŸããããŸããå埩åã®ãããœãããŠã§ã¢ãæ§ç¯ããããã®ãå ç¢ãªãšã©ãŒåŠçããã£ã³ã»ã«ãããã³ã¿ã€ã ã¢ãŠãã®éèŠãªéèŠæ§ãæ¢æ±ããŸããã
éåæããã°ã©ãã³ã°ã®äžçã¯åºå€§ã§ãããã¿ã¹ã¯ãç¿åŸããããšãæãéèŠãªã¹ãããã§ããå®éšãéå§ããŠãã ãããã¢ããªã±ãŒã·ã§ã³ã®ã·ãŒã±ã³ã·ã£ã«ãªI/OããŠã³ãã®éšåãã䞊è¡ã¿ã¹ã¯ã䜿çšããããã«å€æããããã©ãŒãã³ã¹ã®åäžãèªåã§ç¢ºèªããŠãã ããã䞊è¡åŠçã®åãåãå ¥ããã°ã次äžä»£ã®é«æ§èœPythonã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããããã®æºåãæŽããŸãã